package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.RedisData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Autowired
    private RedisTemplate redisTemplate;
    private static final ExecutorService CACHE_RESET = Executors.newFixedThreadPool(10);

    @Override
    public Result<Shop> queryShowById(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        Map shopMap = redisTemplate.opsForHash().entries(key);
        if (!shopMap.isEmpty()) {
            return Result.ok(BeanUtil.fillBeanWithMap(shopMap, new Shop(), false));
        }
        return getShopResult(id, key);
    }

    private Result<Shop> getShopResult(Long id, String key) {
        Shop shop = super.getById(id);
        if (shop == null) {
            Shop shopOfNull = new Shop();
            redisTemplate.opsForHash().putAll(key, BeanUtil.beanToMap(shopOfNull));
            redisTemplate.expire(key, RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        redisTemplate.opsForHash().putAll(key, BeanUtil.beanToMap(shop));
        redisTemplate.expire(key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        unLock(RedisConstants.LOCK_SHOP_KEY);
        return Result.ok(shop);
    }

    //互斥锁 解决缓存击穿
    @Override
    public Result<Shop> queryShowWithPassThrough(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        Map shopMap = redisTemplate.opsForHash().entries(key);
        if (!shopMap.isEmpty()) {
            return Result.ok(BeanUtil.fillBeanWithMap(shopMap, new Shop(), false));
        }
        while (!tryLock(RedisConstants.LOCK_SHOP_KEY)) {

        }
        return getShopResult(id, key);
    }
    //逻辑过期 解决缓存击穿
    public Result<Shop> queryShowWithLogicThrough(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        Map shopMap = redisTemplate.opsForHash().entries(key);
        if (!shopMap.isEmpty()) {
            RedisData<Shop> redisData = BeanUtil.fillBeanWithMap(shopMap, new RedisData(), false);
            LocalDateTime expireTime = redisData.getExpireTime();
            if (expireTime.isAfter(LocalDateTime.now())) {
                return Result.ok(redisData.getData());
            }
        }
        Shop shop = super.getById(id);
        if (tryLock(key)) {
            try {
                CACHE_RESET.submit(() -> {
                    RedisData<Shop> redisData1 = new RedisData<>();
                    redisData1.setExpireTime(LocalDateTime.now());
                    redisData1.setData(shop);
                    redisTemplate.opsForHash().putAll(key, BeanUtil.beanToMap(redisData1));
                });
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                this.unLock(key);
            }
        }
        return Result.ok(shop);
    }

    @Override
    @Transactional
    public Result<String> updateShopById(Shop shop) {
        String key = RedisConstants.CACHE_SHOP_KEY + shop.getId();
        boolean b = super.updateById(shop);
        if (b) {
            redisTemplate.delete(key);
            return Result.ok("更新成功");
        } else {
            return Result.fail("更新失败");
        }
    }


    private boolean tryLock(String key) {
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(isLock);
    }

    private void unLock(String key) {
        redisTemplate.delete(key);
    }
}
